package com.linsen.gateway.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.common.base.Stopwatch;
import com.linsen.core.common.util.DateUtil;
import com.linsen.core.common.util.RequestHolder;
import com.linsen.core.common.util.ResponseUtil;
import com.linsen.core.common.util.Result;
import com.linsen.core.rule.constant.RuleConstant;
import com.linsen.core.rule.model.Deny;
import com.linsen.core.rule.service.IRuleCacheService;
import com.linsen.gateway.service.ISafeRuleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 安全规则业务实现类
 *
 * @author zhaoyong
 * @date 2023/3/13
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SafeRuleServiceImpl implements ISafeRuleService {
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final IRuleCacheService ruleCacheService;

    @Override
    public Mono<Void> filterDeny(ServerWebExchange exchange) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        try {
            URI originUri = getOriginRequestUri(exchange);
            String requestIp = RequestHolder.getServerHttpRequestIpAddress(request);
            String requestMethod = request.getMethodValue();
            AtomicBoolean forbid = new AtomicBoolean(Boolean.FALSE);
            // 从缓存中获取黑名单信息
            Set<Object> denys = this.ruleCacheService.getDenys(requestIp);
            denys.addAll(this.ruleCacheService.getDenys());
            // 检查是否在黑名单中
            checkDenys(forbid, denys, originUri, requestMethod);
            log.debug("黑名单检查完成 - {}", stopwatch.stop());
            if (forbid.get()) {
                log.info("属于黑名单地址 - {}", originUri.getPath());
                return ResponseUtil.webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE,
                        HttpStatus.NOT_ACCEPTABLE, Result.get(HttpStatus.NOT_ACCEPTABLE.value(), "已列入黑名单，访问受限"));
            }
        } catch (Exception e) {
            log.error("黑名单检查异常: {} - {}", e.getMessage(), stopwatch.stop());
        }
        return null;
    }

    /**
     * 获取网关请求URI
     *
     * @param exchange ServerWebExchange
     * @return URI
     */
    private URI getOriginRequestUri(ServerWebExchange exchange) {
        return exchange.getRequest().getURI();
    }

    /**
     * 检查是否满足黑名单的条件
     *
     * @param forbid        是否黑名单判断
     * @param denys         黑名列表
     * @param uri           资源
     * @param requestMethod 请求方法
     */
    private void checkDenys(AtomicBoolean forbid, Set<Object> denys, URI uri, String requestMethod) {
        for (Object denyObject : denys) {
            Deny deny = JSONObject.parseObject(denyObject.toString(), Deny.class);
            if (antPathMatcher.match(deny.getRequestUri(), uri.getPath()) && RuleConstant.DENY_OPEN.equals(deny.getStatus())) {
                if (RuleConstant.ALL.equalsIgnoreCase(deny.getRequestMethod())
                        || StrUtil.equalsIgnoreCase(requestMethod, deny.getRequestMethod())) {
                    if (StrUtil.isNotBlank(deny.getStartTime()) && StrUtil.isNotBlank(deny.getEndTime())) {
                        if (DateUtil.between(DateUtil.parseLocalTime(deny.getStartTime(), DateUtil.DATETIME_FORMATTER),
                                DateUtil.parseLocalTime(deny.getEndTime(), DateUtil.DATETIME_FORMATTER))) {
                            forbid.set(Boolean.TRUE);
                        }
                    } else {
                        forbid.set(Boolean.TRUE);
                    }
                }
            }
            if (forbid.get()) {
                break;
            }
        }
    }
}
